《Android 基础(四十三)》 SnapHelper

1. 简介

先看看英文介绍

1
2
3
4
5
6
7
/**
* Class intended to support snapping for a {@link RecyclerView}.
* <p>
* SnapHelper tries to handle fling as well but for this to work properly, the
* {@link RecyclerView.LayoutManager} must implement the {@link ScrollVectorProvider} interface or
* you should override {@link #onFling(int, int)} and handle fling manually.
*/

可以看出SnapHelper是对RecyclerView的一种拓展功能,支持snapping。SnapHelper通过处理RecyclerView的fling,来达到要展示的效果,但是这需要一个前提条件,就是RecyclerView的LayoutManager必须实现ScrollVectorProvider接口或者手动实现onFling接口,自己实现fling处理。

SnapHelper的实现原理是监听RecyclerView.OnFlingListener中的onFling接口。LinearSnapHelper&PagerSnapHelper是抽象类SnapHelper的具体实现。
区别在于:
LinerSnapHelper,可滑动多页,居中显示;
PagerSnapHelper,每次只能滑动一页,居中显示;

2. 基本实现

2.1 横向LinearSnapHelper

1
2
3
4
5
6
7
LinearLayoutManager llm = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
CardAdapter cardAdapter = new CardAdapter(this);
LinearSnapHelper linearSnapHelper = new LinearSnapHelper();

mRvContent.setLayoutManager(llm);
mRvContent.setAdapter(cardAdapter);
linearSnapHelper.attachToRecyclerView(mRvContent);

这里写图片描述

2.2 纵向LinearSnapHelper

1
2
3
4
5
6
7
LinearLayoutManager llm = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
CardAdapter cardAdapter = new CardAdapter(this);
LinearSnapHelper linearSnapHelper = new LinearSnapHelper();

mRvContent.setLayoutManager(llm);
mRvContent.setAdapter(cardAdapter);
linearSnapHelper.attachToRecyclerView(mRvContent);

这里写图片描述

2.3 PagerSnapHelper(以横向为例)

1
2
3
4
5
6
7
LinearLayoutManager llm = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
CardAdapter cardAdapter = new CardAdapter(this);
PagerSnapHelper pagerSnapHelper = new PagerSnapHelper();

mRvContent.setLayoutManager(llm);
mRvContent.setAdapter(cardAdapter);
pagerSnapHelper.attachToRecyclerView(mRvContent);

这里写图片描述

一般步骤:

  1. 创建SnapHelper;
  2. attachToRecyclerView。

3. 源码分析

SnapHelper源码结构:

这里写图片描述

SnapHelper是一个抽象类,无构造方法。其中有三个抽象方法是需要子类去实现的

这里写图片描述

方法名 描述
calculateDistanceToFinalSnap 计算第二个参数对应的ItemView当前的坐标与需要对齐的坐标之间的距离。返回一个int[2]数组,分别对应x轴和y轴方向上的距离
findSnapView 找到最接近对齐位置的view,该view称为SanpView,对应的position称为SnapPosition。如果返回null,就表示没有需要对齐的View,也就不会做滚动对齐调整
findTargetSnapPosition 根据Fling操作的速率(参数velocityX和参数velocityY)找到需要滚动到的targetSnapPosition,该位置对应的View就是targetSnapView。如果找不到targetSnapPosition,就返回RecyclerView.NO_POSITION

3.1 attachToRecyclerView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void attachToRecyclerView(@Nullable RecyclerView recyclerView)
throws IllegalStateException {
if (mRecyclerView == recyclerView) {
return; // nothing to do
}
if (mRecyclerView != null) {
destroyCallbacks();
}
mRecyclerView = recyclerView;
if (mRecyclerView != null) {
setupCallbacks();
mGravityScroller = new Scroller(mRecyclerView.getContext(),
new DecelerateInterpolator());
snapToTargetExistingView();
}
}

1.判断绑定的RecyclerView 和之前的是否相同,不重复绑定;
2.如果传入的RecyclerView不为空,则执行destoryCallbacks(), 移除滚动监听和onFlingListener

1
2
3
4
private void destroyCallbacks() {
mRecyclerView.removeOnScrollListener(mScrollListener);
mRecyclerView.setOnFlingListener(null);
}

3.赋值成员变量mRecyclerView
4.setupCallbacks()和destoryCallbacks()对应

1
2
3
4
5
6
7
8
private void setupCallbacks() throws IllegalStateException {
//如果onFlingListener已经设置过,则抛出异常
if (mRecyclerView.getOnFlingListener() != null) {
throw new IllegalStateException("An instance of OnFlingListener already set.");
}
mRecyclerView.addOnScrollListener(mScrollListener);
mRecyclerView.setOnFlingListener(this);
}

5.创建mGravityScroller
6.snapToTargetExistingView()
这个方法在这里调用一次,其实更关键的调用地方是在OnScrollListener中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Handles the snap on scroll case.
private final RecyclerView.OnScrollListener mScrollListener =
new RecyclerView.OnScrollListener() {
boolean mScrolled = false;

@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_IDLE && mScrolled) {
mScrolled = false;
//当滑动状态为idle状态时并且刚结束滚动
snapToTargetExistingView();
}
}

@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if (dx != 0 || dy != 0) {
mScrolled = true;
}
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void snapToTargetExistingView() {
if (mRecyclerView == null) {
return;
}
LayoutManager layoutManager = mRecyclerView.getLayoutManager();
if (layoutManager == null) {
return;
}
//找到SnapView
View snapView = findSnapView(layoutManager);
if (snapView == null) {
return;
}
//计算偏移量然后平滑的滑动过去
int[] snapDistance = calculateDistanceToFinalSnap(layoutManager, snapView);
if (snapDistance[0] != 0 || snapDistance[1] != 0) {
mRecyclerView.smoothScrollBy(snapDistance[0], snapDistance[1]);
}
}

3.2 onFling

Fling操作:
手指在屏幕上滑动RecyclerView后松手,RecyclerView中的内容会靠着惯性继续往之前滑动的方向继续滚动直到停止,这个过程叫做Fling。Fling操作从手指离开屏幕瞬间被触发,在滚动停止时结束。

刚才在setupCallbacks()中设置的onFlingerListener

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public boolean onFling(int velocityX, int velocityY) {
LayoutManager layoutManager = mRecyclerView.getLayoutManager();
if (layoutManager == null) {
return false;
}
RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
if (adapter == null) {
return false;
}
//获取触发fling的最小速度
int minFlingVelocity = mRecyclerView.getMinFlingVelocity();
//横向或者纵向必须有一个滑动速度可以触发fling,snapFromFling和上面滚动监听器中的实现思路类似
return (Math.abs(velocityY) > minFlingVelocity || Math.abs(velocityX) > minFlingVelocity)
&& snapFromFling(layoutManager, velocityX, velocityY);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private boolean snapFromFling(@NonNull LayoutManager layoutManager, int velocityX,
int velocityY) {
//这就是为何前文中提到需要实现ScrollVectorProvider否则旧得自己实现onFling接口
if (!(layoutManager instanceof ScrollVectorProvider)) {
return false;
}

//创建scroller
RecyclerView.SmoothScroller smoothScroller = createSnapScroller(layoutManager);
if (smoothScroller == null) {
return false;
}

//找到目标snap位置
int targetPosition = findTargetSnapPosition(layoutManager, velocityX, velocityY);
if (targetPosition == RecyclerView.NO_POSITION) {
return false;
}

//平滑的滚动过去
smoothScroller.setTargetPosition(targetPosition);
layoutManager.startSmoothScroll(smoothScroller);
return true;
}

有兴趣的盆友可以去查阅下LinearSnapHelper和PagerSnapHelper的源码。
推荐源码阅读网站
androidxref.com

4. 源码

RecyclerViewGallery

5. 推荐开源库

RecyclerViewSnap

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×